////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                                            //
//                                                         WNProject                                                          //
//                                                                                                                            //
//            This file is distributed under the BSD 2-Clause open source license. See License.txt for details.               //
//                                                                                                                            //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#pragma once

#ifndef __WN_CONTAINERS_SERIALIZER_INL__
#define __WN_CONTAINERS_SERIALIZER_INL__

#ifndef __WN_CONTAINERS_SERIALIZER_H__
    #error "WNSerializer.inl should never be included directly. Please include WNSerializer.h instead"
#endif

#include "WNCore/inc/WNTypes.h"
#include "WNCore/inc/WNEndian.h"
#include "WNCore/inc/WNAssert.h"
#include "WNStrings/inc/WNStrings.h"
#include "WNMemory/inc/WNManipulation.h"
#include "WNContainers/inc/WNSerializer.h"

namespace WNContainers {
    template <typename Type>
    WN_SIZE_T WNSerializer<Type>::Serialize(WNDataBuffer& _dataBuffer, WN_UINT32 _serializeFlags) const {
        WN_RELEASE_ASSERT_DESC(WN_FALSE, "This type has no associated serializer");
    }

    WN_INLINE WNSerializer<WN_UINT8>::WNSerializer(WN_UINT8& _item) :
        mNumber(_item) {
    }

    WN_INLINE WN_SIZE_T WNSerializer<WN_UINT8>::Serialize(WNDataBuffer& _dataBuffer, WN_UINT32 _serializeFlags) const {
        switch(_dataBuffer.GetType()) {
            case ReadBinary: {
                WN_SIZE_T returnedBytes = 0;
                const WN_CHAR* location = _dataBuffer.ReserveBytes(sizeof(WN_UINT8), returnedBytes);

                WN_DEBUG_ASSERT(returnedBytes == sizeof(WN_UINT8));

                WNMemory::WNMemCpy(&mNumber, location, sizeof(WN_UINT8));

                return(sizeof(WN_UINT8));
            }
            case WriteBinary: {
                WN_SIZE_T returnedBytes = 0;
                WN_CHAR* location = _dataBuffer.ReserveBytes(sizeof(WN_UINT8), returnedBytes);

                WN_DEBUG_ASSERT(returnedBytes == sizeof(WN_UINT8));

                WNMemory::WNMemCpy(location, &mNumber, sizeof(WN_UINT8));

                return(sizeof(WN_UINT8));
            }
            case ReadText: {
                WN_SIZE_T returnedBytes = 0;
                WN_CHAR* location = _dataBuffer.ReserveBytes(4, returnedBytes);
                WN_UINT32 number;
                WN_SIZE_T outSize = WNStrings::WNReadUInt32(location, number, returnedBytes - 1);

                mNumber = number & 0xFF;

                outSize += 1; //consume the space that is supposed to be there

                return(outSize);
            }
            case WriteText: {
                WN_SIZE_T returnedBytes = 0;
                WN_CHAR* location = _dataBuffer.ReserveBytes(4, returnedBytes);

                WN_DEBUG_ASSERT(returnedBytes == 4);

                WN_SIZE_T outSize = WNStrings::WNWriteUInt32(location, mNumber, 3);

                WN_DEBUG_ASSERT_DESC(outSize > 0, "Could not write number, reserved bytes too small?");

                location[outSize++] = ' ';

                return(outSize);
            }
        }

        return(0);
    }

    WN_INLINE WNSerializer<WN_INT32>::WNSerializer(WN_INT32& _item) :
        mNumber(_item) {
    }

    WN_INLINE WN_SIZE_T WNSerializer<WN_INT32>::Serialize(WNDataBuffer& _dataBuffer, WN_UINT32 _serializeFlags) const {
        switch(_dataBuffer.GetType()) {
            case ReadBinary: {
                WN_SIZE_T returnedBytes = 0;
                const WN_CHAR* location = _dataBuffer.ReserveBytes(sizeof(WN_INT32), returnedBytes);

                WN_DEBUG_ASSERT(returnedBytes == sizeof(WN_INT32));

                WNMemory::WNMemCpy(&mNumber, location, sizeof(WN_INT32));
                WNFromBigEndian(mNumber);

                return(sizeof(WN_INT32));
            }
            case WriteBinary: {
                WN_SIZE_T returnedBytes = 0;
                WN_CHAR* location = _dataBuffer.ReserveBytes(sizeof(WN_INT32), returnedBytes);

                WN_DEBUG_ASSERT(returnedBytes == sizeof(WN_INT32));

                WN_INT32 number = mNumber;

                WNToBigEndian(number);
                WNMemory::WNMemCpy(location, &number, sizeof(WN_INT32));

                return(sizeof(WN_INT32));
            }
            case ReadText: {
                WN_SIZE_T returnedBytes = 0;
                WN_CHAR* location = _dataBuffer.ReserveBytes(16, returnedBytes);
                WN_SIZE_T outSize = WNStrings::WNReadInt32(location, mNumber, returnedBytes - 1);

                outSize += 1; //consume the space that is supposed to be there

                return(outSize);
            }
            case WriteText: {
                WN_SIZE_T returnedBytes = 0;
                WN_CHAR* location = _dataBuffer.ReserveBytes(17, returnedBytes);

                WN_DEBUG_ASSERT(returnedBytes == 17);

                WN_SIZE_T outSize = WNStrings::WNWriteInt32(location, mNumber, 16);

                WN_DEBUG_ASSERT_DESC(outSize > 0, "Could not write number, reserved bytes too small?");

                location[outSize++] = ' ';

                return(outSize);
            }
        }

        return(0);
    }

    WN_INLINE WNSerializer<WN_UINT32>::WNSerializer(WN_UINT32& _item) :
        mNumber(_item) {
    }

    WN_INLINE WN_SIZE_T WNSerializer<WN_UINT32>::Serialize(WNDataBuffer& _dataBuffer, WN_UINT32 _serializeFlags) const {
        switch(_dataBuffer.GetType()) {
            case ReadBinary: {
                WN_SIZE_T returnedBytes = 0;
                const WN_CHAR* location = _dataBuffer.ReserveBytes(sizeof(WN_UINT32), returnedBytes);

                WN_DEBUG_ASSERT(returnedBytes == sizeof(WN_UINT32));

                WNMemory::WNMemCpy(&mNumber, location, sizeof(WN_UINT32));
                WNFromBigEndian(mNumber);

                return(sizeof(WN_UINT32));
            }
            case WriteBinary: {
                WN_SIZE_T returnedBytes = 0;
                WN_CHAR* location = _dataBuffer.ReserveBytes(sizeof(WN_UINT32), returnedBytes);

                WN_DEBUG_ASSERT(returnedBytes == sizeof(WN_UINT32));

                WN_UINT32 number = mNumber;

                WNToBigEndian(number);
                WNMemory::WNMemCpy(location, &number, sizeof(WN_UINT32));

                return(sizeof(WN_UINT32));
            }
            case ReadText: {
                WN_SIZE_T returnedBytes = 0;
                WN_CHAR* location = _dataBuffer.ReserveBytes(16, returnedBytes);
                WN_SIZE_T outSize = WNStrings::WNReadUInt32(location, mNumber, returnedBytes - 1);

                outSize += 1; //consume the space that is supposed to be there

                return(outSize);
            }
            case WriteText: {
                WN_SIZE_T returnedBytes = 0;
                WN_CHAR* location = _dataBuffer.ReserveBytes(16, returnedBytes);

                WN_DEBUG_ASSERT(returnedBytes == 16);

                WN_SIZE_T outSize = WNStrings::WNWriteUInt32(location, mNumber, 15);

                WN_DEBUG_ASSERT_DESC(outSize > 0, "Could not write number, reserved bytes too small?");

                location[outSize++] = ' ';

                return(outSize);
            }
        }

        return(0);
    }

    WN_INLINE WNSerializer<WN_INT64>::WNSerializer(WN_INT64& _item) :
        mNumber(_item) {
    }

    WN_INLINE WN_SIZE_T WNSerializer<WN_INT64>::Serialize(WNDataBuffer& _dataBuffer, WN_UINT32 _serializeFlags) const {
        switch(_dataBuffer.GetType()) {
            case ReadBinary: {
                WN_SIZE_T returnedBytes = 0;
                const WN_CHAR* location = _dataBuffer.ReserveBytes(sizeof(WN_INT64), returnedBytes);

                WN_DEBUG_ASSERT(returnedBytes == sizeof(WN_INT64));

                WNMemory::WNMemCpy(&mNumber, location, sizeof(WN_INT64));
                WNFromBigEndian(mNumber);

                return(sizeof(WN_INT64));
            }
            case WriteBinary: {
                WN_SIZE_T returnedBytes = 0;
                WN_CHAR* location = _dataBuffer.ReserveBytes(sizeof(WN_INT64), returnedBytes);

                WN_DEBUG_ASSERT(returnedBytes == sizeof(WN_INT64));

                WN_INT64 number = mNumber;

                WNToBigEndian(number);
                WNMemory::WNMemCpy(location, &number, sizeof(WN_INT64));

                return(sizeof(WN_INT64));
            }
            case ReadText: {
                WN_SIZE_T returnedBytes = 0;
                WN_CHAR* location = _dataBuffer.ReserveBytes(23, returnedBytes);
                WN_SIZE_T outSize = WNStrings::WNReadInt64(location, mNumber, returnedBytes - 1);

                outSize += 1; //consume the space that is supposed to be there

                return(outSize);
            }
            case WriteText: {
                WN_SIZE_T returnedBytes = 0;
                WN_CHAR* location = _dataBuffer.ReserveBytes(24, returnedBytes);

                WN_DEBUG_ASSERT(returnedBytes == 24);

                WN_SIZE_T outSize = WNStrings::WNWriteInt64(location, mNumber, 23);

                WN_DEBUG_ASSERT_DESC(outSize > 0, "Could not write number, reserved bytes too small?");

                location[outSize++] = ' ';

                return(outSize);
            }
        }

        return(0);
    }

    WN_INLINE WNSerializer<WN_UINT64>::WNSerializer(WN_UINT64& _item) :
        mNumber(_item) {
    }

    WN_INLINE WN_SIZE_T WNSerializer<WN_UINT64>::Serialize(WNDataBuffer& _dataBuffer, WN_UINT32 _serializeFlags) const {
        switch(_dataBuffer.GetType()) {
            case ReadBinary: {
                WN_SIZE_T returnedBytes = 0;
                const WN_CHAR* location = _dataBuffer.ReserveBytes(sizeof(WN_UINT64), returnedBytes);

                WN_DEBUG_ASSERT(returnedBytes == sizeof(WN_UINT64));

                WNMemory::WNMemCpy(&mNumber, location, sizeof(WN_UINT64));
                WNFromBigEndian(mNumber);

                return(sizeof(WN_UINT64));
            }
            case WriteBinary: {
                WN_SIZE_T returnedBytes = 0;
                WN_CHAR* location = _dataBuffer.ReserveBytes(sizeof(WN_UINT64), returnedBytes);

                WN_DEBUG_ASSERT(returnedBytes == sizeof(WN_UINT64));

                WN_UINT64 number = mNumber;

                WNToBigEndian(number);
                WNMemory::WNMemCpy(location, &number, sizeof(WN_UINT64));

                return(sizeof(WN_UINT64));
            }
            case ReadText: {
                WN_SIZE_T returnedBytes = 0;
                WN_CHAR* location = _dataBuffer.ReserveBytes(23, returnedBytes);
                WN_SIZE_T outSize = WNStrings::WNReadUInt64(location, mNumber, returnedBytes - 1);

                outSize += 1; //consume the space that is supposed to be there

                return(outSize);
            }
            case WriteText: {
                WN_SIZE_T returnedBytes = 0;
                WN_CHAR* location = _dataBuffer.ReserveBytes(23, returnedBytes);

                WN_DEBUG_ASSERT(returnedBytes == 23);

                WN_SIZE_T outSize = WNStrings::WNWriteUInt64(location, mNumber, 22);

                WN_DEBUG_ASSERT_DESC(outSize > 0, "Could not write number, reserved bytes too small?");

                location[outSize++] = ' ';

                return(outSize);
            }
        }

        return(0);
    }

    WN_INLINE WNSerializer<WN_CHAR*>::WNSerializer(WN_CHAR*& _item, WN_SIZE_T _length) :
        mString(_item),
        mLength(_length) {
    }

    WN_INLINE WN_SIZE_T WNSerializer<WN_CHAR*>::Serialize(WNDataBuffer& _dataBuffer, WN_UINT32 _serializeFlags) const {
        switch(_dataBuffer.GetType()) {
            case ReadBinary:
            case ReadText: {
                WN_SIZE_T returnedBytes = 0;
                const WN_CHAR* location = _dataBuffer.ReserveBytes(mLength, returnedBytes);

                WN_DEBUG_ASSERT(returnedBytes == mLength);

                WNMemory::WNMemCpy(mString, location, mLength);
                return(mLength);
            }
            case WriteBinary:
            case WriteText: {
                WN_SIZE_T returnedBytes = 0;
                WN_CHAR* location = _dataBuffer.ReserveBytes(mLength, returnedBytes);

                WN_DEBUG_ASSERT(returnedBytes == mLength);

                WNMemory::WNMemCpy(location, mString, mLength);

                return(mLength);
            }
        }

        return(0);
    }

    WN_INLINE WNSerializer<WN_FLOAT32>::WNSerializer(WN_FLOAT32& _item) :
        mNumber(_item) {
    }

    WN_INLINE WN_SIZE_T WNSerializer<WN_FLOAT32>::Serialize(WNDataBuffer& _dataBuffer, WN_UINT32 _serializeFlags) const {
        switch(_dataBuffer.GetType()) {
            case ReadBinary: {
                WN_SIZE_T returnedBytes = 0;
                const WN_CHAR* location = _dataBuffer.ReserveBytes(sizeof(WN_FLOAT32), returnedBytes);

                WN_DEBUG_ASSERT(returnedBytes == sizeof(WN_FLOAT32));

                WNMemory::WNMemCpy(&mNumber, location, sizeof(WN_FLOAT32));
                WNFromBigEndian(mNumber);

                return(sizeof(WN_INT32));
            }
            case WriteBinary: {
                WN_SIZE_T returnedBytes = 0;
                WN_CHAR* location = _dataBuffer.ReserveBytes(sizeof(WN_FLOAT32), returnedBytes);

                WN_DEBUG_ASSERT(returnedBytes == sizeof(WN_FLOAT32));

                WN_FLOAT32 number = mNumber;

                WNToBigEndian(number);
                WNMemory::WNMemCpy(location, &number, sizeof(WN_FLOAT32));

                return(sizeof(WN_INT32));
            }
            case ReadText: {
                WN_SIZE_T returnedBytes = 0;
                WN_CHAR* location = _dataBuffer.ReserveBytes(20, returnedBytes);
                WN_SIZE_T outSize = WNStrings::WNReadFloat32(location, mNumber, returnedBytes - 1);

                outSize += 1; //consume the space that is supposed to be there

                return(outSize);
            }
            case WriteText: {
                WN_SIZE_T returnedBytes = 0;
                WN_CHAR* location = _dataBuffer.ReserveBytes(20, returnedBytes);

                WN_DEBUG_ASSERT(returnedBytes == 20);

                WN_SIZE_T outSize = WNStrings::WNWriteFloat32(location, mNumber, 19);

                WN_DEBUG_ASSERT_DESC(outSize > 0, "Could not write number, reserved bytes too small?");

                location[outSize++] = ' ';

                return(outSize);
            }
        }

        return(0);
    }
}

#endif // __WN_CONTAINERS_SERIALIZER_INL__